/*
 *  Copyright (C) Extenex Corporation 2001
 *  Copyright (C) Compaq Computer Corporation, 1998, 1999
 *  Copyright (C) Intrinsyc, Inc., 2002
 *
 * (C) Copyright 2006 Marvell International Ltd.
 * All Rights Reserved
 *
 *  PXA USB controller driver - Endpoint zero management
 *
 *  Please see:
 *    linux/Documentation/arm/SA1100/SA1100_USB
 *  for more info.
 *
 *  02-May-2002
 *   Frank Becker (Intrinsyc) - derived from sa1100 usb_ctl.c
 *
 */

#include "types.h"
#include "arch.h"
#include "time.h"
#include "mhn-regs.h"
#include "xllp_u2d.h"
#include "mhn_u2d_ctl.h"
#include "util.h"
#include "asm/errno.h"
#include "mhn_u2d_ep0.h"

#define MHN_U2D_EP0_DBG 0
#if MHN_U2D_EP0_DBG
#define DBGOUT(x) x
#else
#define DBGOUT(x)     /* gcc will remove all the debug code for us */
#endif

enum { true = 1, false = 0 };
typedef int bool;
#ifndef MIN
#define MIN( a, b ) ((a)<(b)?(a):(b))
#endif

extern int usb_speed;

EP0_state ep0_state = EP0_IDLE;

/***************************************************************************
  Prototypes
 ***************************************************************************/
/* "setup handlers" -- the main functions dispatched to by the
   .. isr. These represent the major "modes" of endpoint 0 operation */
static void sh_setup_begin(void);               /* setup begin (idle) */
static void sh_write( void );                   /* writing data */
static int  read_fifo( usb_dev_request_t * p );
static void write_fifo( void );
static void get_descriptor( usb_dev_request_t * pReq );
static void queue_and_start_write( void * p, int req, int act );
void print_buf(char *buf, int num);
void usb_driver_speed(int speed);

/***************************************************************************
  Inline Helpers
 ***************************************************************************/

inline int type_code_from_request( __u8 by ) { return (( by >> 4 ) & 3); }

/* print string descriptor */
static inline void psdesc( string_desc_t * p )
{
    int i;
    int nchars = ( p->bLength - 2 ) / sizeof( __u16 );
    for( i = 0 ; i < nchars ; i++ ) {
        DBGOUT(printf( "%c", (char) p->bString[i] ));
    }
    DBGOUT(printf( "'\n" ));
}

#if VERBOSITY
/* "pcs" == "print control status" */
static inline void pcs( void )
{
    __u32 u2dcsr0 = U2DCSR0;

    DBGOUT(printf("u2dcsr0 %08X =%s%s%s%s%s%s%s%s%s\n",
        u2dcsr0,
        (u2dcsr0 & U2DCSR0_IPA) ? " ipa" : "",
        (u2dcsr0 & U2DCSR0_SA) ? " sa" : "",
        (u2dcsr0 & U2DCSR0_RNE) ? " rne" : "",
        (u2dcsr0 & U2DCSR0_FST) ? " fst" : "",
        (u2dcsr0 & U2DCSR0_SST) ? " sst" : "",
        (u2dcsr0 & U2DCSR0_DME) ? " dme" : "",
        (u2dcsr0 & U2DCSR0_FTF) ? " ftf" : "",
        (u2dcsr0 & U2DCSR0_IPR) ? " ipr" : "",
        (u2dcsr0 & U2DCSR0_OPC) ? " opc" : ""));

}
static inline void preq( usb_dev_request_t * pReq )
{
    static char * tnames[] = { "dev", "intf", "ep", "oth" };
    static char * rnames[] = { "std", "class", "vendor", "???" };
    char * psz;
    switch( pReq->bRequest ) {
        case GET_STATUS:        psz = "get stat"; break;
        case CLEAR_FEATURE:     psz = "clr feat"; break;
        case SET_FEATURE:       psz = "set feat"; break;
        case SET_ADDRESS:       psz = "set addr"; break;
        case GET_DESCRIPTOR:    psz = "get desc"; break;
        case SET_DESCRIPTOR:    psz = "set desc"; break;
        case GET_CONFIGURATION: psz = "get cfg"; break;
        case SET_CONFIGURATION: psz = "set cfg"; break;
        case GET_INTERFACE:     psz = "get intf"; break;
        case SET_INTERFACE:     psz = "set intf"; break;
        case SYNCH_FRAME:       psz = "synch frame"; break;
        default:                psz = "unknown"; break;
    }
    DBGOUT(printf( "- [%s: %s req to %s. dir=%s]\n", psz,
            rnames[ (pReq->bmRequestType >> 5) & 3 ],
            tnames[ pReq->bmRequestType & 3 ],
            ( pReq->bmRequestType & 0x80 ) ? "in" : "out" ));
}

#else
static inline void pcs( void ){}
static inline void preq( usb_dev_request_t *x){}
#endif

/***************************************************************************
  Globals
 ***************************************************************************/
static const char pszMe[] = "usbep0: ";

/* pointer to current setup handler */
static void (*current_handler)(void) = sh_setup_begin;

/* global write struct to keep write
   ..state around across interrupts */
static struct {
    unsigned char *p;
    int bytes_left;
} wr;

/***************************************************************************
  Public Interface
 ***************************************************************************/

/* reset received from HUB (or controller just went nuts and reset by itself!)
   so udc core has been reset, track this state here  */
void ep0_reset(void)
{
    DBGOUT(printf( "%sep0_reset\n", pszMe));
    /* reset state machine */
    current_handler = sh_setup_begin;
    wr.p = 0;
    wr.bytes_left = 0;
    usbd_info.address=0;
}

/* handle interrupt for endpoint zero */
void ep0_int_hndlr( int flag )
{
    DBGOUT(printf( "%sep0_int_hndlr\n", pszMe));
    pcs();
    (*current_handler)();
}

usb_cdc_line_coding g_bspLineCode = { GS_DEFAULT_DTE_RATE,
                                   GS_DEFAULT_CHAR_FORMAT,
                                   GS_DEFAULT_PARITY,
                                   GS_DEFAULT_DATA_BITS };

 extern int usb_connected;
/***************************************************************************
  Setup Handlers
 ***************************************************************************/
/*
 * sh_setup_begin()
 * This setup handler is the "idle" state of endpoint zero. It looks for OPC
 * (OUT packet ready) to see if a setup request has been been received from the
 * host.
 *
 */
static void sh_setup_begin( void )
{
    DBGOUT(printf("sh_setup_begin@@@@@@@@@@@@\n"));
    usb_dev_request_t req;
    int request_type;
    int n;
    __u32 cs_reg_in = U2DCSR0;
    unsigned char *p = 0;
    unsigned short wLen = 0, wTotLen;
    int bSupport = 1;
    if ( (cs_reg_in & U2DCSR0_OPC) == 0 ) {
        /* we can get here early...if so, we'll int again in a moment  */
        DBGOUT(printf( "%ssetup begin: no OUT packet available. Exiting\n", pszMe ));
        goto sh_sb_end;
    }

    if( ((cs_reg_in & U2DCSR0_SA) == 0) && (ep0_state == EP0_IN_DATA_PHASE)) {
        DBGOUT(printf( "%ssetup begin: premature status\n", pszMe ));

        /* premature status, reset tx fifo and go back to idle state*/
        U2DCSR0 = U2DCSR0_OPC | U2DCSR0_FTF;

        ep0_state = EP0_IDLE;
        return;
    }

    if( (U2DCSR0 & U2DCSR0_RNE) == 0) {
        // zero-length OUT
        DBGOUT(printf( "zero-length OUT! Good Out Status\n"));
        goto sh_sb_end;
    }

    /* read the setup request */
    n = read_fifo( &req );
    if ( n != sizeof( req ) ) {
        DBGOUT(printf( "%ssetup begin: fifo READ ERROR wanted %d bytes got %d. "
                " Stalling out...\n",
                pszMe, sizeof( req ), n ));
        /* force stall, serviced out */
        U2DCSR0 = U2DCSR0_FST;
        goto sh_sb_end;
    }

    /* Is it a standard request? (not vendor or class request) */
    request_type = type_code_from_request( req.bmRequestType );

    if ( request_type != 0 ) {
        DBGOUT(printf("Vendor/Class Command !!\n"));
        wLen = req.wLength;
        switch( req.bRequest ) {
                case USB_CDC_REQ_SET_LINE_CODING:
                  DBGOUT(printf("USB_CDC_REQ_SET_LINE_CODING\n"));
                    p = (unsigned char*)&g_bspLineCode;
                    wLen    = 0;
                    wTotLen = 0;
                    bSupport = 0;
                    break;

                case USB_CDC_REQ_GET_LINE_CODING:
                    DBGOUT(printf("USB_CDC_REQ_GET_LINE_CODING\n"));
                    p = (unsigned char*)&g_bspLineCode;
                    wLen = (unsigned short)(sizeof(usb_cdc_line_coding)<wLen?sizeof(usb_cdc_line_coding):wLen);
                    wTotLen = sizeof(usb_cdc_line_coding);
                    break;

                case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
                    DBGOUT(printf("USB_CDC_REQ_SET_CONTROL_LINE_STATE\n"));
                    p  = 0;
                    wLen = 0;
                    wTotLen = 0;
                    bSupport = 0;
                    if (req.wValue & 0x03) {
                        DBGOUT(printf("\n$$$Congratulations!!!You can Transmit data by Usb Now!!!!!!!!!!!\n\n"));
                        usb_connected = 1;

                        dump_u2d_reg();
                    }
                    else {
                        DBGOUT(printf("Device is Closed now, wait for it open!\n"));
                    }
                    break;

                default:
                    /* Unknown vendor/class request */
                    DBGOUT(printf("Unknown vendor/class request\n"));
                    p  = 0;
                    wLen = 0;
                    wTotLen = 0;
                    break;
        }
        if (0 == bSupport) {
            DBGOUT(printf("bSupport == 0\n"));
            if (p) {
                U2DCSR0 |= (U2DCSR0_OPC | U2DCSR0_SA);
                ostdelay(1000); //wait for the data phase of USB_CDC_REQ_SET_LINE_CODING to finish
                read_fifo((usb_dev_request_t *)p);
                p = 0;
                wLen = 0;
                wTotLen = 0;
            }
            U2DCSR0 |= (U2DCSR0_OPC | U2DCSR0_SA);
            U2DCSR0 |= U2DCSR0_IPR;
        }
        if( wLen ) {
            DBGOUT(printf("wLen != 0!\n"));
            queue_and_start_write( p,
                    req.wLength,
                    wLen );
                }
        goto sh_sb_end;
    }


    else { // class specific commands
        switch( req.bRequest ) {

            case GET_DESCRIPTOR:
                DBGOUT(printf( "%sGET_DESCRIPTOR\n", pszMe ));
                get_descriptor( &req );
                break;

            case SET_INTERFACE:
                DBGOUT(printf( "%sSET_INTERFACE TODO...\n", pszMe));
                break;

            case SET_DESCRIPTOR:
                DBGOUT(printf( "%sSET_DESCRIPTOR TODO...\n", pszMe ));
                break;

            case SET_CONFIGURATION:
                DBGOUT(printf( "%sSET_CONFIGURATION %d\n", pszMe, req.wValue));

            case SET_ADDRESS:
                DBGOUT(printf( "%sSET_ADDRESS handled by U2D\n", pszMe));
                break;

            case SET_FEATURE:
                DBGOUT(printf( "%sSET_FEATURE handled by UDC\n", pszMe));
                break;

            case CLEAR_FEATURE:
                DBGOUT(printf( "%sCLEAR_FEATURE handled by UDC\n", pszMe));
                break;

            case GET_CONFIGURATION:
                DBGOUT(printf( "%sGET_CONFIGURATION handled by UDC\n", pszMe ));
                break;

            case GET_STATUS:
                DBGOUT(printf( "%s%sGET_STATUS handled by UDC\n", pszMe ));
                break;

            case GET_INTERFACE:
                DBGOUT(printf( "%sGET_INTERFACE handled by UDC\n", pszMe));
                break;

            case SYNCH_FRAME:
                DBGOUT(printf( "%sSYNCH_FRAME handled by UDC\n", pszMe ));
                break;
            default :
                DBGOUT(printf("%sunknown request 0x%x\n", pszMe, req.bRequest));
                break;
        } /* switch( bRequest ) */
    }

sh_sb_end:
    TRACEOUT
    return;
}

/*
 * sh_write()
 *
 * Due to UDC bugs we push everything into the fifo in one go.
 * Using interrupts just didn't work right...
 * This should be ok, since control request are small.
 */
static void sh_write()
{
    DBGOUT(printf( "sh_write\n" ));
    do {
        write_fifo();
    } while( ep0_state != EP0_END_XFER);
}

/***************************************************************************
  Other Private Subroutines
 ***************************************************************************/
/*
 * queue_and_start_write()
 * data == data to send
 * req == bytes host requested
 * act == bytes we actually have
 *
 * Sets up the global "wr"-ite structure and load the outbound FIFO
 * with data.
 *
 */
static void queue_and_start_write( void * data, int req, int act )
{
    DBGOUT(printf( "write start: bytes requested=%d actual=%d\n", req, act));

    wr.p = (unsigned char*) data;
    wr.bytes_left = MIN( act, req );

    ep0_state = EP0_IN_DATA_PHASE;
    sh_write();

    return;
}
/*
 * write_fifo()
 * Stick bytes in the endpoint zero FIFO.
 *
 */
static void write_fifo( void )
{
    int bytes_this_time = MIN( wr.bytes_left, EP0_FIFO_SIZE );
    int bytes_written = 0, adjusted = 0, count = 0;
    __u32 aligned_buf[EP0_FIFO_SIZE>>2];
    __u8  *data=(__u8 *)aligned_buf;

    DBGOUT(printf("%s  wr.p=0x%08x, u2dcsr0 %x\n", __FUNCTION__, wr.p, U2DCSR0));

    if ((U2DCSR0 &  U2DCSR0_IPR) == U2DCSR0_IPR)//last write not finished  yet
        return;
    memcpy(data, wr.p, bytes_this_time);

    /* 4-bytes word */
    while( bytes_this_time >= 4 ) {
        U2DDR0 = *((__u32*)data);
        data += 4;
        bytes_written += 4;
        bytes_this_time -= 4;
    }
    if(bytes_this_time == 3) {
        adjusted = 1;
        bytes_written += 3;
        U2DDR0 = *((__u32*)data);
    } else if (bytes_this_time == 2) {
        bytes_written += 2;
        *((__u16*)&U2DDR0) = *((__u16*)data);
        DBGOUT(printf("wirted %d\n",*((__u16*)data)));
    }
    else if (bytes_this_time) {
        bytes_written += 1;
        *((__u8*)&U2DDR0) = *((__u8*)data);
    }

    wr.p += bytes_written;
    wr.bytes_left -= bytes_written;

    usbd_info.stats.ep0_bytes_written += bytes_written;

    if( (wr.bytes_left==0)) {
        wr.p = 0;
        //even bytes_written = EP0_FIFO_SIZE we must send a zero packet

        /* We always end the transfer with a short or zero length packet */
        ep0_state = EP0_END_XFER;
        current_handler = sh_setup_begin;

        /* Let the packet go... */
        U2DCSR0 |= U2DCSR0_OPC;//       issue 1669966, xj
        if(adjusted) U2DCSR0 = U2DCSR0_IPR|U2DCSR0_SA/*|U2DCSR0_OPC*/|U2DCSR0_IPA;
        else U2DCSR0 = U2DCSR0_IPR|U2DCSR0_SA/*|U2DCSR0_OPC*/;

        DBGOUT(printf( "write fifo: wr.bytes_left=%d U2DCSR0=%x U2DCSR0=%x\n", wr.bytes_left, u2dcsr0, U2DCSR0));
    }
    else {
        U2DCSR0 = U2DCSR0_IPR;
        count = 10000;
        do {
        }while ((U2DCSR0 & U2DCSR0_IPR) && count--);
        DBGOUT(printf( "U2DCSR0 %x, set IPR, count %d\n", U2DCSR0, count));
    }

    DBGOUT(printf( "write fifo: bytes sent=%d, bytes left=%d,U2DCSR0 %x\n", bytes_written, wr.bytes_left,U2DCSR0));
}

/*
 * read_fifo()
 * Read bytes out of FIFO and put in request.
 * Called to do the initial read of setup requests
 * from the host. Return number of bytes read.
 *
 */
static int read_fifo( usb_dev_request_t * request )
{
    DBGOUT(printf("read_fifo@@@@@@@\n"));
    int bytes_read = 0;
    unsigned char * pOut = (unsigned char*) request;

    int u2dcsr0 = U2DCSR0;
    DBGOUT(printf("u2dcsr0 = %x, the count in fifo is %d\n", u2dcsr0, U2DBCR0));

    if( (u2dcsr0 & SETUP_READY) == SETUP_READY) {
        /* ok it's a setup command */
        while( U2DCSR0 & U2DCSR0_RNE) {
            if( bytes_read >= sizeof( usb_dev_request_t)) {
                /* We've already read enought o fill usb_dev_request_t.
                 * Our tummy is full. Go barf...
                 */
                DBGOUT(printf( "%sread_fifo(): read failure\n", pszMe ));
                usbd_info.stats.ep0_fifo_read_failures++;
                break;
            }

            *((__u32*)pOut) = U2DDR0;
            pOut += 4;
            bytes_read +=4;
        }
    }
    DBGOUT(printf( "read_fifo %d bytes\n", bytes_read));

    DBGOUT(printf("SETUP bmRequestType:%02x. bRequest: %02x. Value: %04x, Index: %04x, Length: %04x\n",
                request->bmRequestType, request->bRequest,
                       request->wValue, request->wIndex, request->wLength));

    /* clear SA & OPC */
    //U2DCSR0 = SETUP_READY;

    usbd_info.stats.ep0_bytes_read += bytes_read;
    return bytes_read;
}

/*
 * get_descriptor()
 * Called from sh_setup_begin to handle data return
 * for a GET_DESCRIPTOR setup request.
 *
 * +-----+------------------------------------------------+-----------------+
 * | dev | cfg1 | intf 1 | ep 1..N | intf 2 | ep 1..N |...| cfg2 .......... |
 * +-----+------------------------------------------------+-----------------+
 */
static void get_descriptor( usb_dev_request_t * pReq )
{
    string_desc_t * pString;
//  ep_desc_t * pEndpoint = 0;
//  config_desc_t *pcfg = 0;

    desc_t * pDesc = pxa_usb_get_descriptor_ptr();
    int type = pReq->wValue >> 8;
    int idx  = pReq->wValue & 0xFF;

    DBGOUT(printf( "%sget_descriptor for %d\n", pszMe, type ));
    switch( type ) {
        case USB_DESC_DEVICE:
            /* return device descritpor */
            queue_and_start_write( &pDesc->dev,
                    pReq->wLength,
                    pDesc->dev.bLength );
            break;

            // return device qualifier descriptor
        case USB_DESC_QUALIFIER:
            queue_and_start_write( &pDesc->qua,
                    pReq->wLength,
                    pDesc->qua.bLength );
            break;

            // return config descriptor buffer, cfg, intf 1..N,  ep 1..N
        case USB_DESC_CONFIG:
            {
                int i,len;
                config_desc_t *cfg =(config_desc_t*) (pDesc->cdb);

                len=0;
                for( i=0; i<pDesc->dev.bNumConfigurations; i++) {
                    len += __le16_to_cpu(cfg->wTotalLength);
                    cfg = (config_desc_t*) ( (unsigned char*) cfg
                            + __le16_to_cpu(cfg->wTotalLength)) ;
                }
                print_buf((char *)pDesc->cdb,len);
                queue_and_start_write( pDesc->cdb,
                        pReq->wLength,
                        len);
            }
            break;

            // return other speed config descriptor buffer
        case USB_DESC_OTHER_SPEED_CONFIG:
            {
                int i,len;
                config_desc_t *cfg =(config_desc_t*) (pDesc->cdb);

                if(usb_speed == USB_SPEED_HIGH) usb_driver_speed(USB_SPEED_FULL);
                else usb_driver_speed(USB_SPEED_HIGH);
                len=0;
                for( i=0; i<pDesc->dev.bNumConfigurations; i++) {
                    len += __le16_to_cpu(cfg->wTotalLength);
                    cfg = (config_desc_t*) ( (unsigned char*) cfg
                            + __le16_to_cpu(cfg->wTotalLength)) ;
                }

                queue_and_start_write( pDesc->cdb,
                        pReq->wLength,
                        len);
                usb_driver_speed(usb_speed);
            }
            break;

            // not quite right, since doesn't do language code checking
        case USB_DESC_STRING:
            pString = pxa_usb_get_string_descriptor( idx );
            if ( pString ) {
                if ( idx != 0 ) {  // if not language index
                    DBGOUT(printf( "%sReturn string %d: ", pszMe, idx ));
                    psdesc( pString );
                }
                queue_and_start_write( pString,
                        pReq->wLength,
                        pString->bLength );
            }
            else {
                DBGOUT(printf("%sunkown string index %d Stall.\n", pszMe, idx ));
            }
            break;

        default :
            DBGOUT(printf("%sunknown descriptor type %d. Stall.\n", pszMe, type ));
            break;

    }
}

/* end usb_ep0.c - who needs this comment? */
